/*
** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com)
**
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of version 2 of the GNU Library General
** Public License as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** Library General Public License for more details.  To obtain a
** copy of the GNU Library General Public License, write to the Free
** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
** Any permitted reproduction of these routines, in whole or in part,
** must bear this legend.
**
**
** gui.c
**
** GUI routines
** $Id: gui.c,v 1.2 2001/04/27 14:37:11 neil Exp $
*/

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <noftypes.h>
#include <nes_ppu.h>
#include <nes_apu.h>
#include <nesinput.h>
#include <nes.h>
#include <log.h>
#include <osd.h>

#include <bitmap.h>

#include <gui.h>
#include <gui_elem.h>
#include <vid_drv.h>

/* TODO: oh god */
/* 8-bit GUI color table */
rgb_t gui_pal[GUI_TOTALCOLORS] = {
    {0x00, 0x00, 0x00}, /* black      */
    {0x3F, 0x3F, 0x3F}, /* dark gray  */
    {0x7F, 0x7F, 0x7F}, /* gray       */
    {0xBF, 0xBF, 0xBF}, /* light gray */
    {0xFF, 0xFF, 0xFF}, /* white      */
    {0xFF, 0x00, 0x00}, /* red        */
    {0x00, 0xFF, 0x00}, /* green      */
    {0x00, 0x00, 0xFF}, /* blue       */
    {0xFF, 0xFF, 0x00}, /* yellow     */
    {0xFF, 0xAF, 0x00}, /* orange     */
    {0xFF, 0x00, 0xFF}, /* purple     */
    {0x3F, 0x7F, 0x7F}, /* teal       */
    {0x00, 0x2A, 0x00}, /* dk. green  */
    {0x00, 0x00, 0x3F}  /* dark blue  */
};

/**************************************************************/
#include <pcx.h>
#include <nesstate.h>
static bool option_drawsprites = true;

/* save a PCX snapshot */
void gui_savesnap(void)
{
    char filename[PATH_MAX];
    nes_t* nes = nes_getcontextptr();

    if (osd_makesnapname(filename, PATH_MAX) < 0)
        return;

    if (pcx_write(filename, nes->vidbuf, nes->ppu->curpal))
        return;

    gui_sendmsg(GUI_GREEN, "Screen saved to %s", filename);
}

/* Show/hide sprites (hiding sprites useful for making maps) */
void gui_togglesprites(void)
{
    option_drawsprites ^= true;
    ppu_displaysprites(option_drawsprites);
    gui_sendmsg(GUI_GREEN, "Sprites %s", option_drawsprites ? "displayed" : "hidden");
}

/* Set the frameskip policy */
void gui_togglefs(void)
{
    nes_t* machine = nes_getcontextptr();

    machine->autoframeskip ^= true;
    if (machine->autoframeskip)
        gui_sendmsg(GUI_YELLOW, "automatic frameskip");
    else
        gui_sendmsg(GUI_YELLOW, "unthrottled emulation");
}

/* display rom information */
void gui_displayinfo() { gui_sendmsg(GUI_ORANGE, (char*)rom_getinfo(nes_getcontextptr()->rominfo)); }

void gui_toggle_chan(int chan)
{
#define FILL_CHAR 0x7C  /* ASCII 124 '|' */
#define BLANK_CHAR 0x7F /* ASCII 127   [delta] */
    static bool chan_enabled[6] = {true, true, true, true, true, true};

    chan_enabled[chan] ^= true;
    apu_setchan(chan, chan_enabled[chan]);

    gui_sendmsg(GUI_ORANGE,
                "%ca %cb %cc %cd %ce %cext",
                chan_enabled[0] ? FILL_CHAR : BLANK_CHAR,
                chan_enabled[1] ? FILL_CHAR : BLANK_CHAR,
                chan_enabled[2] ? FILL_CHAR : BLANK_CHAR,
                chan_enabled[3] ? FILL_CHAR : BLANK_CHAR,
                chan_enabled[4] ? FILL_CHAR : BLANK_CHAR,
                chan_enabled[5] ? FILL_CHAR : BLANK_CHAR);
}

void gui_setfilter(int filter_type)
{
    char* types[3] = {"no", "lowpass", "weighted"};
    static int last_filter = 2;

    if (last_filter == filter_type || filter_type < 0 || filter_type > 2)
        return;

    apu_setfilter(filter_type);
    gui_sendmsg(GUI_ORANGE, "%s filter", types[filter_type]);
    last_filter = filter_type;
}
/**************************************************************/

enum
{
    GUI_WAVENONE,
    GUI_WAVELINE,
    GUI_WAVESOLID,
    GUI_NUMWAVESTYLES
};

enum
{
    BUTTON_UP,
    BUTTON_DOWN
};

/* TODO: roll options into a structure */
static message_t msg;
static bool option_showfps = false;
static bool option_showgui = false;
static int option_wavetype = GUI_WAVENONE;
static bool option_showpattern = false;
static bool option_showoam = false;
static int pattern_col = 0;

/* timimg variables */
static bool gui_fpsupdate = false;
static int gui_ticks = 0;
static int gui_fps = 0;
static int gui_refresh = 60; /* default to 60Hz */

static int mouse_x, mouse_y, mouse_button;

static bitmap_t* gui_surface;

/* Put a pixel on our bitmap- just for GUI use */
INLINE void gui_putpixel(int x_pos, int y_pos, uint8 color) { gui_surface->line[y_pos][x_pos] = color; }

/* Line drawing */
static void gui_hline(int x_pos, int y_pos, int length, uint8 color)
{
    while (length--)
        gui_putpixel(x_pos++, y_pos, color);
}

static void gui_vline(int x_pos, int y_pos, int height, uint8 color)
{
    while (height--)
        gui_putpixel(x_pos, y_pos++, color);
}

/* Rectangles */
static void gui_rect(int x_pos, int y_pos, int width, int height, uint8 color)
{
    gui_hline(x_pos, y_pos, width, color);
    gui_hline(x_pos, y_pos + height - 1, width, color);
    gui_vline(x_pos, y_pos + 1, height - 2, color);
    gui_vline(x_pos + width - 1, y_pos + 1, height - 2, color);
}

static void gui_rectfill(int x_pos, int y_pos, int width, int height, uint8 color)
{
    while (height--)
        gui_hline(x_pos, y_pos++, width, color);
}

/* Draw the outline of a button */
static void gui_buttonrect(int x_pos, int y_pos, int width, int height, bool down)
{
    uint8 color1, color2;

    if (down)
    {
        color1 = GUI_GRAY;
        color2 = GUI_WHITE;
    }
    else
    {
        color1 = GUI_WHITE;
        color2 = GUI_GRAY;
    }

    gui_hline(x_pos, y_pos, width - 1, color1);
    gui_vline(x_pos, y_pos + 1, height - 2, color1);
    gui_hline(x_pos, y_pos + height - 1, width, color2);
    gui_vline(x_pos + width - 1, y_pos, height - 1, color2);
}

/* Text blitting */
INLINE void gui_charline(char ch, int x_pos, int y_pos, uint8 color)
{
    int count = 8;
    while (count--)
    {
        if (ch & (1 << count))
            gui_putpixel(x_pos, y_pos, color);
        x_pos++;
    }
}

static void gui_putchar(const uint8* dat, int height, int x_pos, int y_pos, uint8 color)
{
    while (height--)
        gui_charline(*dat++, x_pos, y_pos++, color);
}

/* Return length of text in pixels */
static int gui_textlen(char* str, font_t* font)
{
    int pixels = 0;
    int num_chars = strlen(str);

    while (num_chars--)
        pixels += font->character[(*str++ - 32)].spacing;

    return pixels;
}

/* Simple textout() type function */
static int gui_textout(char* str, int x_pos, int y_pos, font_t* font, uint8 color)
{
    int x_new;
    int num_chars = strlen(str);
    int code;

    x_new = x_pos;

    while (num_chars--)
    {
        /* Turn ASCII code into letter */
        code = *str++;
        if (code > 0x7F)
            code = 0x7F;
        code -= 32; /* normalize */
        gui_putchar(font->character[code].lines, font->height, x_new, y_pos, color);
        x_new += font->character[code].spacing;
    }

    /* Return the length in pixels */
    return (x_new - x_pos);
}

/* Draw bar-/button-type text */
static int gui_textbar(char* str, int x_pos, int y_pos, font_t* font, uint8 color, uint8 bgcolor, bool buttonstate)
{
    int width = gui_textlen(str, &small);

    /* Fill the 'button' */
    gui_buttonrect(x_pos, y_pos, width + 3, font->height + 3, buttonstate);
    gui_rectfill(x_pos + 1, y_pos + 1, width + 1, font->height + 1, bgcolor);

    /* Print the text */
    return gui_textout(str, x_pos + 2, y_pos + 2, font, color);
}

/* Draw the mouse pointer */
static void gui_drawmouse(void)
{
    int ythresh, xthresh;
    int i, j, color;

    ythresh = gui_surface->height - mouse_y - 1;
    for (j = 0; j < CURSOR_HEIGHT; j++)
    {
        if (ythresh < 0)
            continue;

        xthresh = gui_surface->width - mouse_x - 1;
        for (i = 0; i < CURSOR_WIDTH; i++)
        {
            if (xthresh < 0)
                continue;

            color = cursor[(j * CURSOR_WIDTH) + i];

            if (color)
                gui_putpixel(mouse_x + i, mouse_y + j, cursor_color[color]);
            xthresh--;
        }
        ythresh--;
    }
}

void gui_tick(int ticks)
{

    static int fps_counter = 0;

    gui_ticks += ticks;
    fps_counter += ticks;

    if (fps_counter >= gui_refresh)
    {
        fps_counter -= gui_refresh;
        gui_fpsupdate = true;
    }
}

/* updated in sync with the timer interrupt */
static void gui_tickdec(void)
{
#ifdef NOFRENDO_DEBUG
    static int hertz_ticks = 0;
#endif
    int ticks = gui_ticks;

    if (0 == ticks)
        return;

    gui_ticks = 0;

#ifdef NOFRENDO_DEBUG
    /* Check for corrupt memory block every 10 seconds */
    hertz_ticks += ticks;
    if (hertz_ticks >= (10 * gui_refresh))
    {
        hertz_ticks -= (10 * gui_refresh);
        mem_checkblocks();
    }
#endif

    /* TODO: bleh */
    if (msg.ttl > 0)
    {
        msg.ttl -= ticks;
        if (msg.ttl < 0)
            msg.ttl = 0;
    }
}

/* Update the FPS display */
static void gui_updatefps(void)
{
    static char fpsbuf[20];

    /* Check to see if we need to do an sprintf or not */
    if (true == gui_fpsupdate)
    {
        sprintf(fpsbuf, "%4d FPS /%4d%%", gui_fps, (gui_fps * 100) / gui_refresh);
        gui_fps = 0;
        gui_fpsupdate = false;
    }

    gui_textout(fpsbuf, gui_surface->width - 1 - 90, 1, &small, GUI_GREEN);
}

/* Turn FPS on/off */
void gui_togglefps(void) { option_showfps ^= true; }

/* Turn GUI on/off */
void gui_togglegui(void) { option_showgui ^= true; }

void gui_togglewave(void) { option_wavetype = (option_wavetype + 1) % GUI_NUMWAVESTYLES; }

void gui_toggleoam(void) { option_showoam ^= true; }

/* TODO: hack! */
void gui_togglepattern(void) { option_showpattern ^= true; }

/* TODO: hack! */
void gui_decpatterncol(void)
{
    if (pattern_col && option_showpattern)
        pattern_col--;
}

/* TODO: hack! */
void gui_incpatterncol(void)
{
    if ((pattern_col < 7) && option_showpattern)
        pattern_col++;
}

/* Downward-scrolling message display */
static void gui_updatemsg(void)
{
    if (msg.ttl)
        gui_textbar(msg.text, 2, gui_surface->height - 10, &small, msg.color, GUI_DKGRAY, BUTTON_UP);
}

/* Little thing to display the waveform */
static void gui_updatewave(int wave_type)
{
#define WAVEDISP_WIDTH 128
    int loop, xofs, yofs;
    int difference, offset;
    float scale;
    uint8 val, oldval;
    int vis_length = 0;
    void* vis_buffer = NULL;
    int vis_bps;
    apu_t apu;

    apu_getcontext(&apu);
    vis_buffer = apu.buffer;
    vis_length = apu.num_samples;
    vis_bps = apu.sample_bits;

    xofs = (NES_SCREEN_WIDTH - WAVEDISP_WIDTH);
    yofs = 1;
    scale = (float)(vis_length / (float)WAVEDISP_WIDTH);

    if (NULL == vis_buffer)
    {
        /* draw centerline */
        gui_hline(xofs, yofs + 0x20, WAVEDISP_WIDTH, GUI_GRAY);
        gui_textbar("no sound", xofs + 40, yofs + 0x20 - 4, &small, GUI_RED, GUI_DKGRAY, BUTTON_UP);
    }
    else if (GUI_WAVELINE == wave_type)
    {
        /* draw centerline */
        gui_hline(xofs, yofs + 0x20, WAVEDISP_WIDTH, GUI_GRAY);

        /* initial old value */
        if (16 == vis_bps)
            oldval = 0x40 - (((((uint16*)vis_buffer)[0] >> 8) ^ 0x80) >> 2);
        else
            oldval = 0x40 - (((uint8*)vis_buffer)[0] >> 2);

        for (loop = 1; loop < WAVEDISP_WIDTH; loop++)
        {
            // val = 0x40 - (vis_buffer[(uint32) (loop * scale)] >> 2);
            if (16 == vis_bps)
                val = 0x40 - (((((uint16*)vis_buffer)[(uint32)(loop * scale)] >> 8) ^ 0x80) >> 2);
            else
                val = 0x40 - (((uint8*)vis_buffer)[(uint32)(loop * scale)] >> 2);
            if (oldval < val)
            {
                offset = oldval;
                difference = (val - oldval) + 1;
            }
            else
            {
                offset = val;
                difference = (oldval - val) + 1;
            }

            gui_vline(xofs + loop, yofs + offset, difference, GUI_GREEN);
            oldval = val;
        }
    }
    /* solid wave */
    else if (GUI_WAVESOLID == wave_type)
    {
        for (loop = 0; loop < WAVEDISP_WIDTH; loop++)
        {
            // val = vis_buffer[(uint32) (loop * scale)] >> 2;
            if (16 == vis_bps)
                val = ((((uint16*)vis_buffer)[(uint32)(loop * scale)] >> 8) ^ 0x80) >> 2;
            else
                val = ((uint8*)vis_buffer)[(uint32)(loop * scale)] >> 2;
            if (val == 0x20)
                gui_putpixel(xofs + loop, yofs + 0x20, GUI_GREEN);
            else if (val < 0x20)
                gui_vline(xofs + loop, yofs + 0x20, 0x20 - val, GUI_GREEN);
            else
                gui_vline(xofs + loop, yofs + 0x20 - (val - 0x20), val - 0x20, GUI_GREEN);
        }
    }

    gui_rect(xofs, yofs - 1, WAVEDISP_WIDTH, 66, GUI_DKGRAY);
}

static void gui_updatepattern(void)
{
    /* Pretty it up a bit */
    gui_textbar("Pattern Table 0", 0, 0, &small, GUI_GREEN, GUI_DKGRAY, BUTTON_UP);
    gui_textbar("Pattern Table 1", 128, 0, &small, GUI_GREEN, GUI_DKGRAY, BUTTON_UP);
    gui_hline(0, 9, 256, GUI_DKGRAY);
    gui_hline(0, 138, 256, GUI_DKGRAY);

    /* Dump the actual tables */
    ppu_dumppattern(gui_surface, 0, 0, 10, pattern_col);
    ppu_dumppattern(gui_surface, 1, 128, 10, pattern_col);
}

static void gui_updateoam(void)
{
    int y;

    y = option_showpattern ? 140 : 0;
    gui_textbar("Current OAM", 0, y, &small, GUI_GREEN, GUI_DKGRAY, BUTTON_UP);
    ppu_dumpoam(gui_surface, 0, y + 9);
}

/* The GUI overlay */
void gui_frame(bool draw)
{
    gui_fps++;
    if (false == draw)
        return;

    gui_surface = vid_getbuffer();

    ASSERT(gui_surface);

    gui_tickdec();

    if (option_showfps)
        gui_updatefps();

    if (option_wavetype != GUI_WAVENONE)
        gui_updatewave(option_wavetype);

    if (option_showpattern)
        gui_updatepattern();

    if (option_showoam)
        gui_updateoam();

    if (msg.ttl)
        gui_updatemsg();

    if (option_showgui)
    {
        osd_getmouse(&mouse_x, &mouse_y, &mouse_button);
        gui_drawmouse();
    }
}

void gui_sendmsg(int color, char* format, ...)
{
    va_list arg;
    va_start(arg, format);
    vsprintf(msg.text, format, arg);

#ifdef NOFRENDO_DEBUG
    log_print("GUI: ");
    log_print(msg.text);
    log_print("\n");
#endif

    va_end(arg);

    msg.ttl = gui_refresh * 2; /* 2 second delay */
    msg.color = color;
}

void gui_setrefresh(int frequency) { gui_refresh = frequency; }

int gui_init(void)
{
    gui_refresh = 60;
    memset(&msg, 0, sizeof(message_t));

    return 0; /* can't fail */
}

void gui_shutdown(void) {}

/*
** $Log: gui.c,v $
** Revision 1.2  2001/04/27 14:37:11  neil
** wheeee
**
** Revision 1.1.1.1  2001/04/27 07:03:54  neil
** initial
**
** Revision 1.26  2000/11/25 20:26:05  matt
** removed fds "system"
**
** Revision 1.25  2000/11/09 14:05:43  matt
** state load fixed, state save mostly fixed
**
** Revision 1.24  2000/11/05 16:37:18  matt
** rolled rgb.h into bitmap.h
**
** Revision 1.23  2000/10/27 12:57:49  matt
** fixed pcx snapshots
**
** Revision 1.22  2000/10/25 00:23:16  matt
** makefiles updated for new directory structure
**
** Revision 1.21  2000/10/23 17:50:47  matt
** adding fds support
**
** Revision 1.20  2000/10/23 15:52:04  matt
** better system handling
**
** Revision 1.19  2000/10/22 19:15:39  matt
** more sane timer ISR / autoframeskip
**
** Revision 1.18  2000/10/17 03:22:37  matt
** cleaning up rom module
**
** Revision 1.17  2000/10/10 13:58:13  matt
** stroustrup squeezing his way in the door
**
** Revision 1.16  2000/10/10 13:03:53  matt
** Mr. Clean makes a guest appearance
**
** Revision 1.15  2000/10/08 17:59:12  matt
** gui_ticks is volatile
**
** Revision 1.14  2000/09/15 04:58:06  matt
** simplifying and optimizing APU core
**
** Revision 1.13  2000/07/31 04:28:46  matt
** one million cleanups
**
** Revision 1.12  2000/07/30 04:29:59  matt
** no more apu_getpcmdata hack
**
** Revision 1.11  2000/07/25 02:20:47  matt
** moved gui palette filth here, for the time being
**
** Revision 1.10  2000/07/24 04:32:05  matt
** bugfix on message delay
**
** Revision 1.9  2000/07/23 15:16:25  matt
** moved non-osd code here
**
** Revision 1.8  2000/07/17 01:52:27  matt
** made sure last line of all source files is a newline
**
** Revision 1.7  2000/07/11 04:40:23  matt
** updated for new screen dimension defines
**
** Revision 1.6  2000/07/09 03:39:33  matt
** small gui_frame cleanup
**
** Revision 1.5  2000/07/06 16:47:18  matt
** new video driver interface
**
** Revision 1.4  2000/06/09 15:12:25  matt
** initial revision
**
*/
